#include "CodecWrapper.hpp"
#include "common/HiCommonLog.hpp"
#include "common/DepLibUV.hpp"
#include "StreamHelper.hpp"

using namespace Hi;

StreamHelper::StreamHelper(Hi::RTPDecoderHandle& handle, uint32_t width, uint32_t height, void* option, const std::function<void()>& onRequestKeyFrame)
    : rtpHandle(handle), 
	scaleWidth(width), 
	scaleHeight(height), 
	requestKeyFrame(onRequestKeyFrame), 
	userOption(option){
		this->videoDecoder = CodecWrapper::CreateCodecWrapper(CodecWrapper::Type::H264);
		this->rbgSize = CodecWrapper::GetRGBSize(this->scaleWidth, this->scaleHeight);
		this->yuvSize = CodecWrapper::GetYUVSize(this->scaleWidth, this->scaleHeight);
		this->videoDecoder->SetScale(this->scaleWidth, this->scaleHeight);
		this->videoDecoder->IsOutYUV(false);
		this->videoDecoder->IsOutRGB(true);
}

// 本函数假设使用tcp传输的数据，不存在乱序问题
void  StreamHelper::ReceivePacket(Hi::RtpPacket* packet) {
	uint16_t seq = packet->GetSequenceNumber();
	this->summary.CheckFirstFrame(seq);

	if (this->summary.IsLowerThanCurrentSeq(seq)) {
		HI_DEBUG("[seq=%u, currentSeq=%u] throw lower seq", seq, this->summary.currentSeq);
		
		printf("[seq=%u, currentSeq=%u] throw lower seq\n", seq, this->summary.currentSeq);
		return;
	}

	this->OnReceivePacketItem(packet);
}

bool StreamHelper::OnReceivePacketItem(Hi::RtpPacket* packet) {
	uint16_t seq = packet->GetSequenceNumber();
	auto bufferPacket = packet;

	if (this->summary.IsWantSeq(seq)) {
		this->ProcessPacket(bufferPacket);
		this->summary.SetNextCurrentSeq(seq);
		return true;
	} 

	if (!this->summary.IsHigherThanCurrentSeq(seq)) {
		return true;
	}
	
	if (this->summary.IsLastProcessMoreThan500ms()) {
		HI_DEBUG("[seq=%u, currentSeq=%u] put in buffer", seq,  this->summary.currentSeq);
		printf("[seq=%u, currentSeq=%u] put in buffer\n", seq,  this->summary.currentSeq);
		return false;
	}
	// timeout
	HI_DEBUG("[seq=%u] timeout\n");
	this->summary.statTimeoutSeqCount++;
	this->requestKeyFrame();
	uint16_t lost = this->summary.GetLostSeqSize(seq);
	this->ProcessPacket(bufferPacket, lost);
	this->summary.SetNextCurrentSeq(seq);
	return true;
}
int StreamHelper::ProcessPacket(Hi::RtpPacket* packet, uint16_t lost) {
	this->summary.lastProcessTime = DepLibUV::GetTime();

	uint32_t ts = packet->GetTimestamp();

	this->summary.statCurrentFrameTime = ts;

	if (this->summary.newFrame) {
		this->summary.frameTime = ts;
		this->summary.newFrame = false;
	} else if (this->summary.IsHigherThanPreviousFrameTime(ts)) {
		HI_DEBUG("lost mark packet frame time:%u", this->summary.frameTime);
		printf("lost mark packet frame time:%u\n", this->summary.frameTime);
		this->summary.statBadFrameCount++;

		// try to decode what is in the buffer
		printf("DecodePacket NULL begin\n");
		this->videoDecoder->DecodePacket(NULL, 0, 1, 1);
		printf("DecodePacket NULL end\n");
	}

	this->summary.frameTime = ts;

	this->summary.statDecodeWithLostCount += lost ? 1 : 0;

	uint8_t *buffer = packet->GetPayload();
	size_t size = packet->GetPayloadLength();
	bool mark = packet->GetMark();
	// Decode packet
	if (!this->videoDecoder->DecodePacket(buffer, (int)size, lost, mark)) {
		HI_DEBUG("decode fail");
		printf("decode fail\n");
		this->summary.statDecodeFailCount++;
		this->requestKeyFrame();
	}

	// Check if it is the last packet of a frame
	if (mark) {
		this->summary.newFrame = true;
		this->summary.statTotalDecodeFrameCount++;

		if (this->videoDecoder->IsKeyFrame()) {
			HI_DEBUG("Get keyframe");
			this->summary.statKeyFrameCount++;
		}

		this->OnHandle();
	}
	return 0;
}
	
void StreamHelper::OnHandle() {
	uint8_t *frame = this->videoDecoder->GetFrame();
	int width = this->videoDecoder->GetWidth();
	int height = this->videoDecoder->GetHeight();
	if (frame && width && height) {
		HI_DEBUG("Get frame [width %d height %d]", width, height);
		this->rtpHandle.OnRGB(this->videoDecoder->GetFrameBgr(), this->rbgSize, this->scaleWidth, this->scaleHeight, userOption);
	}

	if (outputYuv) {
		this->rtpHandle.OnYUV(this->videoDecoder->GetFrameYuv(), this->yuvSize, this->scaleWidth, this->scaleHeight, userOption);
	}
}